﻿using System;
using System.Linq;
using EPiServer;
using EPiServer.Core;
using EPiServer.DataAccess;
using EPiServer.Filters;
using EPiServer.Security;
using TemplateFoundation.Configuration;
using TemplateFoundation.Exceptions;

namespace TemplateFoundation.Core.ContentStructure
{
    /// <summary>
    /// Used to organize pages in a date folder structure
    /// </summary>
    public class DateFolderStructure
    {
        /// <param name="parent">The parent page underneath which the date folder structure should be set up</param>
        /// <remarks>This constructor will organize pages in a date folder structure based on year, month, and day.</remarks>
        public DateFolderStructure(PageReference parent) :
            this(parent, false, false, null, null, true, true, true)
        {

        }

        /// <param name="parent">The parent page underneath which the date folder structure should be set up</param>
        /// <param name="disableMonthFolders">Determines if month folders are used, if not only year folders will be used</param>
        /// <param name="disableDayFolders">Determines if day folders are used, if not only year and month folders will be used</param>
        /// <remarks>This constructor will use the date folder page type ID from site settings</remarks>
        public DateFolderStructure(PageReference parent, bool disableMonthFolders, bool disableDayFolders) :
            this(parent,disableMonthFolders, disableDayFolders,null,null,true,true,true)
        {

        }

        /// <param name="parent">The parent page underneath which the date folder structure should be set up</param>
        /// <param name="disableMonthFolders">Determines if month folders are used, if not only year folders will be used</param>
        /// <param name="disableDayFolders">Determines if day folders are used, if not only year and month folders will be used</param>
        /// <param name="dateFolderPageTypeId">The page type to use for date folders</param>
        public DateFolderStructure(PageReference parent, bool disableMonthFolders, bool disableDayFolders, int dateFolderPageTypeId) :
            this(parent,disableMonthFolders,disableDayFolders,dateFolderPageTypeId,null,true,true,true)
        {

        }

        /// <param name="parent">The parent page underneath which the date folder structure should be set up</param>
        /// <param name="disableMonthFolders">Determines if month folder are used, if not only year folders will be used</param>
        /// <param name="disableDayFolders">Determines if day folders are used, if not only year and month folders will be used</param>
        /// <param name="dateFolderPageTypeId">The page type to use for date folders</param>
        /// <param name="datePropertyName">Name of the PropertyDate property to use for determining date folders</param>
        public DateFolderStructure(PageReference parent, bool disableMonthFolders, bool disableDayFolders, int dateFolderPageTypeId, string datePropertyName) :
            this(parent,disableMonthFolders,disableDayFolders,dateFolderPageTypeId,datePropertyName,true,true,true)
        {

        }

        /// <param name="parent">The parent page underneath which the date folder structure should be set up</param>
        /// <param name="disableMonthFolders">Determines if month folder are used, if not only year folders will be used</param>
        /// <param name="disableDayFolders">Determines if day folders are used, if not only year and month folders will be used</param>
        /// <param name="dateFolderPageTypeId">The page type to use for date folders</param>
        /// <param name="datePropertyName">Name of the PropertyDate property to use for determining date folders</param>
        /// <param name="yearsVisible">Determines the 'Visible in menu' setting when year folders are created</param>
        /// <param name="monthsVisible">Determines the 'Visible in menu' setting when month folders are created</param>
        /// <param name="daysVisible">Determines the 'Visible in menu' setting when day folders are created</param>
        public DateFolderStructure(PageReference parent, bool disableMonthFolders, bool disableDayFolders, int? dateFolderPageTypeId, string datePropertyName, bool yearsVisible, bool monthsVisible, bool daysVisible)
        {
            // Set parent
            if (PageReference.IsNullOrEmpty(parent))
            {
                throw new ArgumentNullException("parent", "No target page specified");
            }
            ParentLink = parent;

            // Set folder settings (if day and/or month folders should be used)
            if (!disableDayFolders && disableMonthFolders)
            {
                throw new NotSupportedException("Day folders can only be used together with month folders, but month folders were disabled");
            }

            UseMonthFolder = !disableMonthFolders;
            UseDayFolder = !disableDayFolders;

            // Set date folder page type
            if(dateFolderPageTypeId == null || !dateFolderPageTypeId.HasValue)
            {
                throw new ArgumentNullException("dateFolderPageTypeId", "No date folder page type ID specified");
            }
            if (dateFolderPageTypeId < 1)
            {
                throw new ArgumentOutOfRangeException("dateFolderPageTypeId", "Invalid page type ID, must be a positive integer");
            }
            DateFolderPageTypeId = dateFolderPageTypeId.Value;

            // Set date property used for determining date folders
            if (string.IsNullOrEmpty(datePropertyName))
            {
                throw new ArgumentNullException("datePropertyName", "No property name specified for the date property used to create date folders");
            }
            DatePropertyName = string.IsNullOrEmpty(datePropertyName) ? "PageStartPublish" : datePropertyName;

            // Set date folders menu visibility
            YearFoldersVisibleInMenu = yearsVisible;
            MonthFoldersVisibleInMenu = monthsVisible;
            DayFoldersVisibleInMenu = daysVisible;
        }

        /// <summary>
        /// Moves a page to a date folder structure underneath a specific parent page
        /// </summary>
        /// <param name="page">The page to move</param>
        public void PutInDateFolder(PageData page)
        {
            DateTime date;

            try
            {
                date = (DateTime)page[DatePropertyName];
            }
            catch (Exception ex)
            {
                throw new TemplateFoundationException(string.Format("The specified property name '{0}' does not map to a valid PropertyDate property", DatePropertyName), ex);
            }

            var target = UseDayFolder ?
                        GetDayContainer(date.Year, date.Month, date.Day, page.LanguageBranch) :
                        UseMonthFolder ? 
                        GetMonthContainer(date.Year, date.Month, page.LanguageBranch) : 
                        GetYearContainer(date.Year, page.LanguageBranch);

            // Verify the container exists for the current language
            PageFactory.Instance.CreateLanguageBranch(target.GetPage(), page.LanguageBranch);

            // Also verify the parent container (if being a date folder, such as the year above the month) exists for the current language
            var targetParent = target.GetPage().ParentLink.GetPage();
            if (targetParent.PageTypeID == DateFolderPageTypeId)
            {
                PageFactory.Instance.CreateLanguageBranch(targetParent, page.LanguageBranch);
            }

            if(page.ParentLink==target)
            {
                return; // Article already exists under the correct container
            }

            // If not yet published we set the pages parent link instead
            if (page.PageLink!=PageReference.EmptyReference)
            {
                DataFactory.Instance.Move(page.PageLink, target, AccessLevel.NoAccess, AccessLevel.NoAccess);
            }
            else
            {
                page.ParentLink = target;
            }
        }

        /// <summary>
        /// Gets or creates the year container for the specified year
        /// </summary>
        /// <param name="year">The year.</param>
        /// <param name="languageBranch">Language branch for the date folder</param>
        /// <returns></returns>
        protected virtual PageReference GetYearContainer(int year, string languageBranch)
        {
            // Find year container, if any
            var criterias = new PropertyCriteriaCollection();

            criterias.Add(new PropertyCriteria
            {
                Name = "PageName",
                Type = PropertyDataType.String,
                Value = year.ToString(),
                Required = true,
                Condition = CompareCondition.Equal
            });

            criterias.Add(new PropertyCriteria
            {
                Name = "PageTypeID",
                Type = PropertyDataType.PageType,
                Value = DateFolderPageTypeId.ToString(),
                Required = true,
                Condition = CompareCondition.Equal
            });

            var result = FindPagesWithCriteriaRegardlessOfLanguage(ParentLink, criterias);

            PageData yearPage;

            if (result.Any())
            {
                yearPage = result[0];

                // Ensure correct language branch exists
                if (yearPage.LanguageBranch!=languageBranch)
                {
                    PageFactory.Instance.CreateLanguageBranch(yearPage, languageBranch);
                }
            }
            else // Create year container if it doesn't exist
            {
                yearPage = DataFactory.Instance.GetDefaultPageData(
                    ParentLink,
                    DateFolderPageTypeId,
                    new LanguageSelector(languageBranch));

                yearPage.PageName = year.ToString();
                yearPage.StartPublish = new DateTime(year, 1, 1);
                yearPage.URLSegment = year.ToString();
                yearPage.VisibleInMenu = YearFoldersVisibleInMenu;

                DataFactory.Instance.Save(yearPage, SaveAction.Publish, AccessLevel.NoAccess);               
            }

            return yearPage.PageLink;
        }

        /// <summary>
        /// Gets or creates the month container for the specified year and month
        /// </summary>
        /// <param name="year">The year.</param>
        /// <param name="month">The month.</param>
        /// <param name="language">Language branch for the date folder</param>
        /// <returns></returns>
        protected virtual PageReference GetMonthContainer(int year, int month, string language)
        {
            var yearContainer = GetYearContainer(year, language);

            // Find month container, if any
            var criterias = new PropertyCriteriaCollection
                                {
                                    new PropertyCriteria
                                        {
                                            Name = "PageName",
                                            Type = PropertyDataType.String,
                                            Value = month.ToString(),
                                            Required = true,
                                            Condition = CompareCondition.Equal
                                        },
                                    new PropertyCriteria
                                        {
                                            Name = "PageTypeID",
                                            Type = PropertyDataType.PageType,
                                            Value = DateFolderPageTypeId.ToString(),
                                            Required = true,
                                            Condition = CompareCondition.Equal
                                        }
                                };

            var monthPage = FindPagesWithCriteriaRegardlessOfLanguage(yearContainer, criterias).FirstOrDefault();

            if (monthPage!=null)
            {
                if (monthPage.LanguageBranch!=language)
                {
                    PageFactory.Instance.CreateLanguageBranch(monthPage, language);
                }

                return monthPage.PageLink;
            }

            monthPage = DataFactory.Instance.GetDefaultPageData(yearContainer, DateFolderPageTypeId);

            monthPage.PageName = month.ToString();
            monthPage.StartPublish = new DateTime(year, month, 1);
            monthPage.URLSegment = month.ToString();
            monthPage.VisibleInMenu = MonthFoldersVisibleInMenu;

            DataFactory.Instance.Save(monthPage, SaveAction.Publish, AccessLevel.NoAccess);

            return monthPage.PageLink;
        }

        /// <summary>
        /// Gets or creates the month container for the specified year and month
        /// </summary>
        /// <param name="year">The year.</param>
        /// <param name="month">Month, a value between 1-12.</param>
        /// <param name="day">The day, a value between 1-31.</param>
        /// <param name="language">The language branch of the date folder</param>
        /// <returns></returns>
        protected virtual PageReference GetDayContainer(int year, int month, int day, string language)
        {
            var monthContainer = GetMonthContainer(year, month, language);

            // Find day container, if any
            var criterias = new PropertyCriteriaCollection
                                {
                                    new PropertyCriteria
                                        {
                                            Name = "PageName",
                                            Type = PropertyDataType.String,
                                            Value = day.ToString(),
                                            Required = true,
                                            Condition = CompareCondition.Equal
                                        },
                                    new PropertyCriteria
                                        {
                                            Name = "PageTypeID",
                                            Type = PropertyDataType.PageType,
                                            Value = DateFolderPageTypeId.ToString(),
                                            Required = true,
                                            Condition = CompareCondition.Equal
                                        }
                                };

            var dayPage = FindPagesWithCriteriaRegardlessOfLanguage(monthContainer, criterias).FirstOrDefault();

            if (dayPage!=null)
            {
                if (dayPage.LanguageBranch!=language)
                {
                    PageFactory.Instance.CreateLanguageBranch(dayPage,language);
                }

                return dayPage.PageLink;
            }

            dayPage = DataFactory.Instance.GetDefaultPageData(monthContainer, DateFolderPageTypeId);

            dayPage.PageName = day.ToString();
            dayPage.StartPublish = new DateTime(year, month, day);
            dayPage.URLSegment = day.ToString();
            dayPage.VisibleInMenu = DayFoldersVisibleInMenu;

            DataFactory.Instance.Save(dayPage, SaveAction.Publish, AccessLevel.NoAccess);

            return dayPage.PageLink;
        }

        /// <summary>
        /// Performs FPWC for all languages until results are found (or we run out of languages)
        /// </summary>
        /// <param name="criteria"></param>
        /// <param name="pageLink">The page underneath which the search should be performed</param>
        /// <returns></returns>
        private static PageDataCollection FindPagesWithCriteriaRegardlessOfLanguage(PageReference pageLink, PropertyCriteriaCollection criteria)
        {
            var result = new PageDataCollection();
            
            foreach (var language in PageFactory.Instance.GetStartPage().PageLanguages)
            {
                result = DataFactory.Instance.FindPagesWithCriteria(pageLink, criteria, language);

                if (result.Any())
                {
                    break;
                }
            }

            return result;
        }

        /// <summary>
        /// Gets the target page, date folder structure will be created underneath this page
        /// </summary>
        public PageReference ParentLink { get; protected set; }

        /// <summary>
        /// Gets whether month containers should be used, otherwise only year date folder will be created
        /// </summary>
        public bool UseMonthFolder { get; protected set; }

        /// <summary>
        /// Gets whether day containers should be used, otherwise only year and month folders will be created
        /// </summary>
        public bool UseDayFolder { get; protected set; }

        /// <summary>
        /// Gets the property name for the date property used to determine date folder structure
        /// </summary>
        public string DatePropertyName { get; protected set; }

        /// <summary>
        /// Gets the ID of the page type used for date folders
        /// </summary>
        public int DateFolderPageTypeId { get; protected set; }

        /// <summary>
        /// Gets whether year folders are visible in menus
        /// </summary>
        public bool YearFoldersVisibleInMenu { get; protected set; }

        /// <summary>
        /// Gets whether month folders are visible in menus
        /// </summary>
        public bool MonthFoldersVisibleInMenu { get; protected set; }

        /// <summary>
        /// Gets whether day folders are visible in menus
        /// </summary>
        public bool DayFoldersVisibleInMenu { get; protected set; }
    }
}
